// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "zenhttp.h"

#include <zenbase/refcount.h>
#include <zencore/compactbinary.h>
#include <zencore/enumflags.h>
#include <zencore/iobuffer.h>
#include <zencore/iohash.h>
#include <zencore/string.h>
#include <zencore/uid.h>
#include <zenhttp/httpcommon.h>

#include <functional>
#include <gsl/gsl-lite.hpp>
#include <list>
#include <map>
#include <regex>
#include <span>
#include <unordered_map>

namespace zen {

class CbPackage;

/** HTTP Server Request
 */
class HttpServerRequest
{
public:
	HttpServerRequest();
	~HttpServerRequest();

	// Synchronous operations

	[[nodiscard]] inline std::string_view RelativeUri() const { return m_Uri; }	 // Returns URI without service prefix
	[[nodiscard]] std::string_view		  RelativeUriWithExtension() const { return m_UriWithExtension; }
	[[nodiscard]] inline std::string_view QueryString() const { return m_QueryString; }

	struct QueryParams
	{
		std::vector<std::pair<std::string_view, std::string_view>> KvPairs;

		std::string_view GetValue(std::string_view ParamName) const
		{
			for (const auto& Kv : KvPairs)
			{
				const std::string_view& Key = Kv.first;

				if (Key.size() == ParamName.size())
				{
					if (0 == StrCaseCompare(Key.data(), ParamName.data(), Key.size()))
					{
						return Kv.second;
					}
				}
			}

			return std::string_view();
		}
	};

	static std::string Decode(std::string_view PercentEncodedString);

	virtual bool TryGetRanges(HttpRanges&) { return false; }

	QueryParams GetQueryParams();

	inline HttpVerb		   RequestVerb() const { return m_Verb; }
	inline HttpContentType RequestContentType() { return m_ContentType; }
	inline HttpContentType AcceptContentType() { return m_AcceptType; }

	inline uint64_t ContentLength() const { return m_ContentLength; }
	Oid				SessionId() const;
	uint32_t		RequestId() const;

	inline bool IsHandled() const { return !!(m_Flags & kIsHandled); }
	inline bool SuppressBody() const { return !!(m_Flags & kSuppressBody); }
	inline void SetSuppressResponseBody() { m_Flags |= kSuppressBody; }

	/** Read POST/PUT payload for request body, which is always available without delay
	 */
	virtual IoBuffer ReadPayload() = 0;

	ZENCORE_API CbObject  ReadPayloadObject();
	ZENCORE_API CbPackage ReadPayloadPackage();

	/** Respond with payload

		No data will have been sent when any of these functions return. Instead, the response will be transmitted
		asynchronously, after returning from a request handler function.

		Note that this is destructive in the sense that the IoBuffer instances referred to by Blobs will be
		moved into our response handler array where they are kept alive, in order to reduce ref-counting storms
	  */
	virtual void WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, std::span<IoBuffer> Blobs)		  = 0;
	virtual void WriteResponse(HttpResponseCode ResponseCode)																  = 0;
	virtual void WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, std::u8string_view ResponseString) = 0;
	virtual void WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, CompositeBuffer& Payload);

	void WriteResponse(HttpResponseCode ResponseCode, CbObject Data);
	void WriteResponse(HttpResponseCode ResponseCode, CbArray Array);
	void WriteResponse(HttpResponseCode ResponseCode, CbPackage Package);
	void WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, std::string_view ResponseString);
	void WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, IoBuffer Blob);

	virtual void WriteResponseAsync(std::function<void(HttpServerRequest&)>&& ContinuationHandler) = 0;

protected:
	enum
	{
		kIsHandled	   = 1 << 0,
		kSuppressBody  = 1 << 1,
		kHaveRequestId = 1 << 2,
		kHaveSessionId = 1 << 3,
	};

	mutable uint32_t m_Flags		 = 0;
	HttpVerb		 m_Verb			 = HttpVerb::kGet;
	HttpContentType	 m_ContentType	 = HttpContentType::kBinary;
	HttpContentType	 m_AcceptType	 = HttpContentType::kUnknownContentType;
	uint64_t		 m_ContentLength = ~0ull;
	std::string_view m_Uri;
	std::string_view m_UriWithExtension;
	std::string_view m_QueryString;
	mutable uint32_t m_RequestId = ~uint32_t(0);
	mutable Oid		 m_SessionId = Oid::Zero;

	inline void SetIsHandled() { m_Flags |= kIsHandled; }

	virtual Oid		 ParseSessionId() const = 0;
	virtual uint32_t ParseRequestId() const = 0;
};

class IHttpPackageHandler : public RefCounted
{
public:
	virtual void	 FilterOffer(std::vector<IoHash>& OfferCids)		   = 0;
	virtual void	 OnRequestBegin()									   = 0;
	virtual IoBuffer CreateTarget(const IoHash& Cid, uint64_t StorageSize) = 0;
	virtual void	 OnRequestComplete()								   = 0;
};

/**
 * Base class for implementing an HTTP "service"
 *
 * A service exposes one or more endpoints with a certain URI prefix
 *
 */

class HttpService
{
public:
	HttpService()		   = default;
	virtual ~HttpService() = default;

	virtual const char*				 BaseUri() const									  = 0;
	virtual void					 HandleRequest(HttpServerRequest& HttpServiceRequest) = 0;
	virtual Ref<IHttpPackageHandler> HandlePackageRequest(HttpServerRequest& HttpServiceRequest);

	// Internals

	inline void SetUriPrefixLength(size_t PrefixLength) { m_UriPrefixLength = (int)PrefixLength; }
	inline int	UriPrefixLength() const { return m_UriPrefixLength; }

private:
	int m_UriPrefixLength = 0;
};

/** HTTP server
 *
 *	Implements the main event loop to service HTTP requests, and handles routing
 *  requests to the appropriate handler as registered via RegisterService
 */
class HttpServer : public RefCounted
{
public:
	virtual void RegisterService(HttpService& Service)					 = 0;
	virtual int	 Initialize(int BasePort, std::filesystem::path DataDir) = 0;
	virtual void Run(bool IsInteractiveSession)							 = 0;
	virtual void RequestExit()											 = 0;
	virtual void Close()												 = 0;
};

struct HttpServerConfig
{
	bool		 IsDedicatedServer = false;	 // Should be set to true for shared servers
	std::string	 ServerClass;				 // Choice of HTTP server implementation
	bool		 ForceLoopback = false;
	unsigned int ThreadCount   = 0;

	struct
	{
		unsigned int AsyncWorkThreadCount	 = 0;
		bool		 IsAsyncResponseEnabled	 = true;
		bool		 IsRequestLoggingEnabled = false;
	} HttpSys;
};

Ref<HttpServer> CreateHttpServer(const HttpServerConfig& Config);

//////////////////////////////////////////////////////////////////////////

class HttpRouterRequest
{
public:
	HttpRouterRequest(HttpServerRequest& Request) : m_HttpRequest(Request) {}

	ZENCORE_API std::string	  GetCapture(uint32_t Index) const;
	inline HttpServerRequest& ServerRequest() { return m_HttpRequest; }

private:
	using MatchResults_t = std::match_results<std::string_view::const_iterator>;

	HttpServerRequest& m_HttpRequest;
	MatchResults_t	   m_Match;

	friend class HttpRequestRouter;
};

inline std::string
HttpRouterRequest::GetCapture(uint32_t Index) const
{
	ZEN_ASSERT(Index < m_Match.size());

	return m_Match[Index];
}

/** HTTP request router helper
 *
 * This helper class allows a service implementer to register one or more
 * endpoints using pattern matching (currently using regex matching)
 *
 * This is intended to be initialized once only, there is no thread
 * safety so you can absolutely not add or remove endpoints once the handler
 * goes live
 */

class HttpRequestRouter
{
public:
	typedef std::function<void(HttpRouterRequest&)> HandlerFunc_t;

	/**
	 * @brief Add pattern which can be referenced by name, commonly used for URL components
	 * @param Id String used to identify patterns for replacement
	 * @param Regex String which will replace the Id string in any registered URL paths
	 */
	void AddPattern(const char* Id, const char* Regex);

	/**
	 * @brief Register a an endpoint handler for the given route
	 * @param Regex Regular expression used to match the handler to a request. This may
	 *		  contain pattern aliases registered via AddPattern
	 * @param HandlerFunc Handler function to call for any matching request
	 * @param SupportedVerbs Supported HTTP verbs for this handler
	 */
	void RegisterRoute(const char* Regex, HandlerFunc_t&& HandlerFunc, HttpVerb SupportedVerbs);

	void ProcessRegexSubstitutions(const char* Regex, StringBuilderBase& ExpandedRegex);

	/**
	 * @brief HTTP request handling function - this should be called to route the
	 *		  request to a registered handler
	 * @param Request Request to route to a handler
	 * @return Function returns true if the request was routed successfully
	 */
	bool HandleRequest(zen::HttpServerRequest& Request);

private:
	struct HandlerEntry
	{
		HandlerEntry(const char* Regex, HttpVerb SupportedVerbs, HandlerFunc_t&& Handler, const char* Pattern)
		: RegEx(Regex, std::regex::icase | std::regex::ECMAScript)
		, Verbs(SupportedVerbs)
		, Handler(std::move(Handler))
		, Pattern(Pattern)
		{
		}

		~HandlerEntry() = default;

		std::regex	  RegEx;
		HttpVerb	  Verbs;
		HandlerFunc_t Handler;
		const char*	  Pattern;

	private:
		HandlerEntry& operator=(const HandlerEntry&) = delete;
		HandlerEntry(const HandlerEntry&)			 = delete;
	};

	std::list<HandlerEntry>						 m_Handlers;
	std::unordered_map<std::string, std::string> m_PatternMap;
};

/** HTTP RPC request helper
 */

class RpcResult
{
	RpcResult(CbObject Result) : m_Result(std::move(Result)) {}

private:
	CbObject m_Result;
};

class HttpRpcHandler
{
public:
	HttpRpcHandler();
	~HttpRpcHandler();

	HttpRpcHandler(const HttpRpcHandler&) = delete;
	HttpRpcHandler operator=(const HttpRpcHandler&) = delete;

	void AddRpc(std::string_view RpcId, std::function<void(CbObject& RpcArgs)> HandlerFunction);

private:
	struct RpcFunction
	{
		std::function<void(CbObject& RpcArgs)> Function;
		std::string							   Identifier;
	};

	std::map<std::string, RpcFunction> m_Functions;
};

bool HandlePackageOffers(HttpService& Service, HttpServerRequest& Request, Ref<IHttpPackageHandler>& PackageHandlerRef);

struct IHttpStatsProvider
{
	virtual void HandleStatsRequest(HttpServerRequest& Request) = 0;
};

struct IHttpStatsService
{
	virtual void RegisterHandler(std::string_view Id, IHttpStatsProvider& Provider)	  = 0;
	virtual void UnregisterHandler(std::string_view Id, IHttpStatsProvider& Provider) = 0;
};

void http_forcelink();	// internal

}  // namespace zen
